package com.jackstraw.jack_base.datasource.elasticsearch.util;

import co.elastic.clients.elasticsearch._types.mapping.BinaryProperty;
import co.elastic.clients.elasticsearch._types.mapping.BooleanProperty;
import co.elastic.clients.elasticsearch._types.mapping.ByteNumberProperty;
import co.elastic.clients.elasticsearch._types.mapping.DateNanosProperty;
import co.elastic.clients.elasticsearch._types.mapping.DateProperty;
import co.elastic.clients.elasticsearch._types.mapping.DoubleNumberProperty;
import co.elastic.clients.elasticsearch._types.mapping.FloatNumberProperty;
import co.elastic.clients.elasticsearch._types.mapping.HalfFloatNumberProperty;
import co.elastic.clients.elasticsearch._types.mapping.IndexOptions;
import co.elastic.clients.elasticsearch._types.mapping.IntegerNumberProperty;
import co.elastic.clients.elasticsearch._types.mapping.KeywordProperty;
import co.elastic.clients.elasticsearch._types.mapping.LongNumberProperty;
import co.elastic.clients.elasticsearch._types.mapping.NestedProperty;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.mapping.ScaledFloatNumberProperty;
import co.elastic.clients.elasticsearch._types.mapping.ShortNumberProperty;
import co.elastic.clients.elasticsearch._types.mapping.TermVectorOption;
import co.elastic.clients.elasticsearch._types.mapping.TextProperty;
import co.elastic.clients.elasticsearch.indices.NumericFielddataFormat;
import com.jackstraw.jack_base.datasource.elasticsearch.aspect.annotation.CollectionType;
import com.jackstraw.jack_base.util.DateTimeUtil;
import com.jackstraw.jack_base.util.ObjectUtil;
import com.jackstraw.jack_base.util.StringUtil;
import com.jackstraw.jack_base.util.function.ReflectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.NullValueType;
import org.springframework.data.elasticsearch.annotations.Similarity;
import org.springframework.data.elasticsearch.annotations.TermVector;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * es工具类
 */
@Slf4j
public class ElasticSearchUtil {

    public static TermVectorOption termVectorOption(TermVector termVector) {
        switch (termVector) {
            case no:
                return TermVectorOption.No;
            case yes:
                return TermVectorOption.Yes;
            case with_positions:
                return TermVectorOption.WithPositions;
            case with_offsets:
                return TermVectorOption.WithOffsets;
            case with_positions_offsets:
                return TermVectorOption.WithPositionsOffsets;
            case with_positions_payloads:
                return TermVectorOption.WithPositionsPayloads;
            case with_positions_offsets_payloads:
                return TermVectorOption.WithPositionsPayloads;
        }
        return null;
    }

    public static IndexOptions indexOptions(org.springframework.data.elasticsearch.annotations.IndexOptions indexOptions) {
        switch (indexOptions) {
            case docs:
                return IndexOptions.Docs;
            case freqs:
                return IndexOptions.Freqs;
            case positions:
                return IndexOptions.Positions;
            case offsets:
                return IndexOptions.Offsets;
        }
        return null;
    }

    public static Property property(Field field, org.springframework.data.elasticsearch.annotations.Field fieldAnnotation, FieldType fieldType) {
        Property.Builder propertyBuilder = new Property.Builder();
        switch (fieldType) {
            case Auto:
                break;
            case Text:
                TextProperty.Builder textPropertyBuilder = new TextProperty.Builder();
                textPropertyBuilder.index(fieldAnnotation.index())
                        .analyzer(fieldAnnotation.analyzer())
                        .searchAnalyzer(fieldAnnotation.searchAnalyzer())
                        .eagerGlobalOrdinals(fieldAnnotation.eagerGlobalOrdinals())
                        .fielddata(fieldAnnotation.fielddata())
                        .norms(fieldAnnotation.norms())
                        .positionIncrementGap(fieldAnnotation.positionIncrementGap() == -1
                                ? 100 : fieldAnnotation.positionIncrementGap())
                        .termVector(termVectorOption(fieldAnnotation.termVector()))
                        .indexPhrases(fieldAnnotation.indexPhrases())
                        .indexOptions(indexOptions(fieldAnnotation.indexOptions()))
                        .similarity(Similarity.Default.equals(fieldAnnotation.similarity())
                                ? Similarity.BM25.toString() : fieldAnnotation.similarity().toString())
                        .store(fieldAnnotation.store())
                //.indexPrefixes() TODO 不知道怎么配置暂时注掉
                ;
                propertyBuilder.text(textPropertyBuilder.build());
                break;
            case Keyword:
                KeywordProperty.Builder keywordPropertyBuilder = new KeywordProperty.Builder();
                keywordPropertyBuilder.index(fieldAnnotation.index())
                        .eagerGlobalOrdinals(fieldAnnotation.eagerGlobalOrdinals())
                        .norms(fieldAnnotation.norms())
                        .ignoreAbove(-1 == fieldAnnotation.ignoreAbove() ? null : fieldAnnotation.ignoreAbove())
                        .indexOptions(indexOptions(fieldAnnotation.indexOptions()))
                        .normalizer(StringUtil.StringUtilsBylang3.isEmpty(fieldAnnotation.normalizer())
                                ? null : fieldAnnotation.normalizer())
                        .similarity(Similarity.Default.equals(fieldAnnotation.similarity())
                                ? Similarity.Boolean.toString() : fieldAnnotation.similarity().toString())
                        .store(fieldAnnotation.store())
                        .docValues(fieldAnnotation.docValues());
                if (NullValueType.String.equals(fieldAnnotation.nullValueType())) {
                    keywordPropertyBuilder.nullValue(fieldAnnotation.nullValue());
                }

                propertyBuilder.keyword(keywordPropertyBuilder.build());
                break;
            case Long:
                LongNumberProperty.Builder longNumberPropertyBuilder = new LongNumberProperty.Builder();
                longNumberPropertyBuilder.index(fieldAnnotation.index())
                        .store(fieldAnnotation.store())
                        .coerce(fieldAnnotation.coerce());
                if (NullValueType.Long.equals(fieldAnnotation.nullValueType())
                        && StringUtil.StringUtilsBylang3.isNotEmpty(fieldAnnotation.nullValue())) {
                    longNumberPropertyBuilder.nullValue(Long.valueOf(fieldAnnotation.nullValue()));
                }
                propertyBuilder.long_(longNumberPropertyBuilder.build());
                break;
            case Integer:
                IntegerNumberProperty.Builder integerNumberPropertyBuilder = new IntegerNumberProperty.Builder();
                integerNumberPropertyBuilder.index(fieldAnnotation.index())
                        .store(fieldAnnotation.store())
                        .coerce(fieldAnnotation.coerce());
                if (NullValueType.Integer.equals(fieldAnnotation.nullValueType())
                        && StringUtil.StringUtilsBylang3.isNotEmpty(fieldAnnotation.nullValue())) {
                    integerNumberPropertyBuilder.nullValue(Integer.valueOf(fieldAnnotation.nullValue()));
                }
                propertyBuilder.integer(integerNumberPropertyBuilder.build());
                break;
            case Short:
                ShortNumberProperty.Builder shortNumberPropertyBuilder = new ShortNumberProperty.Builder();
                shortNumberPropertyBuilder.index(fieldAnnotation.index())
                        .store(fieldAnnotation.store())
                        .coerce(fieldAnnotation.coerce());

                if (NullValueType.Integer.equals(fieldAnnotation.nullValueType())
                        && StringUtil.StringUtilsBylang3.isNotEmpty(fieldAnnotation.nullValue())) {
                    shortNumberPropertyBuilder.nullValue(Integer.valueOf(fieldAnnotation.nullValue()));
                }
                if (NullValueType.Long.equals(fieldAnnotation.nullValueType())
                        && StringUtil.StringUtilsBylang3.isNotEmpty(fieldAnnotation.nullValue())) {
                    shortNumberPropertyBuilder.nullValue(Long.valueOf(fieldAnnotation.nullValue()));
                }
                propertyBuilder.short_(shortNumberPropertyBuilder.build());
                break;
            case Byte:
                ByteNumberProperty.Builder byteNumberPropertyBuilder = new ByteNumberProperty.Builder();
                byteNumberPropertyBuilder.index(fieldAnnotation.index())
                        .store(fieldAnnotation.store())
                        .coerce(fieldAnnotation.coerce());

                if (NullValueType.Integer.equals(fieldAnnotation.nullValueType())
                        && StringUtil.StringUtilsBylang3.isNotEmpty(fieldAnnotation.nullValue())) {
                    byteNumberPropertyBuilder.nullValue(Integer.valueOf(fieldAnnotation.nullValue()));
                }
                propertyBuilder.byte_(byteNumberPropertyBuilder.build());
                break;
            case Double:
                DoubleNumberProperty.Builder doubleNumberPropertyBuilder = new DoubleNumberProperty.Builder();
                doubleNumberPropertyBuilder.index(fieldAnnotation.index())
                        .store(fieldAnnotation.store())
                        .coerce(fieldAnnotation.coerce());
                if (NullValueType.Double.equals(fieldAnnotation.nullValueType())
                        && StringUtil.StringUtilsBylang3.isNotEmpty(fieldAnnotation.nullValue())) {
                    doubleNumberPropertyBuilder.nullValue(Double.valueOf(fieldAnnotation.nullValue()));
                }
                propertyBuilder.double_(doubleNumberPropertyBuilder.build());
                break;
            case Float:
                FloatNumberProperty.Builder floatNumberPropertyBuilder = new FloatNumberProperty.Builder();
                floatNumberPropertyBuilder.index(fieldAnnotation.index())
                        .store(fieldAnnotation.store())
                        .coerce(fieldAnnotation.coerce());
                if (NullValueType.Double.equals(fieldAnnotation.nullValueType())
                        && StringUtil.StringUtilsBylang3.isNotEmpty(fieldAnnotation.nullValue())) {
                    floatNumberPropertyBuilder.nullValue(Float.valueOf(fieldAnnotation.nullValue()));
                }
                propertyBuilder.float_(floatNumberPropertyBuilder.build());
                break;
            case Half_Float:
                HalfFloatNumberProperty.Builder halfFloatNumberPropertyBuilder = new HalfFloatNumberProperty.Builder();
                halfFloatNumberPropertyBuilder.index(fieldAnnotation.index())
                        .store(fieldAnnotation.store())
                        .coerce(fieldAnnotation.coerce());
                if (NullValueType.Double.equals(fieldAnnotation.nullValueType())
                        && StringUtil.StringUtilsBylang3.isNotEmpty(fieldAnnotation.nullValue())) {
                    halfFloatNumberPropertyBuilder.nullValue(Float.valueOf(fieldAnnotation.nullValue()));
                }
                propertyBuilder.halfFloat(halfFloatNumberPropertyBuilder.build());
                break;
            case Scaled_Float:
                ScaledFloatNumberProperty.Builder scaledFloatNumberPropertyBuilder = new ScaledFloatNumberProperty.Builder();
                scaledFloatNumberPropertyBuilder.index(fieldAnnotation.index())
                        .store(fieldAnnotation.store())
                        .coerce(fieldAnnotation.coerce())
                        .scalingFactor(fieldAnnotation.scalingFactor());
                if (NullValueType.Double.equals(fieldAnnotation.nullValueType())
                        && StringUtil.StringUtilsBylang3.isNotEmpty(fieldAnnotation.nullValue())) {
                    scaledFloatNumberPropertyBuilder.nullValue(Double.valueOf(fieldAnnotation.nullValue()));
                }
                propertyBuilder.scaledFloat(scaledFloatNumberPropertyBuilder.build());
                break;
            case Date:
                DateProperty.Builder datePropertyBuilder = new DateProperty.Builder();
                datePropertyBuilder.index(fieldAnnotation.index())
                        .store(fieldAnnotation.store())
                        .ignoreMalformed(fieldAnnotation.ignoreMalformed())
                        .docValues(fieldAnnotation.docValues());
                datePropertyBuilder.format(DateTimeUtil.NORM_DATETIME_MS_PATTERN);
                propertyBuilder.date(datePropertyBuilder.build());
                break;
            case Date_Nanos:
                DateNanosProperty.Builder dateNanosPropertyBuilder = new DateNanosProperty.Builder();
                dateNanosPropertyBuilder.index(fieldAnnotation.index())
                        .store(fieldAnnotation.store())
                        .ignoreMalformed(fieldAnnotation.ignoreMalformed())
                        .docValues(fieldAnnotation.docValues());
                dateNanosPropertyBuilder.format(DateTimeUtil.NORM_DATETIME_MS_PATTERN);
                propertyBuilder.dateNanos(dateNanosPropertyBuilder.build());
                break;
            case Boolean:
                BooleanProperty.Builder booleanPropertyBuilder = new BooleanProperty.Builder();
                booleanPropertyBuilder.index(fieldAnnotation.index())
                        .store(fieldAnnotation.store())
                        .docValues(fieldAnnotation.docValues())
                        .nullValue(Boolean.valueOf(fieldAnnotation.nullValue()));
                propertyBuilder.boolean_(booleanPropertyBuilder.build());
                break;
            case Binary:
                BinaryProperty.Builder binaryPropertyBuilder = new BinaryProperty.Builder();
                binaryPropertyBuilder.store(fieldAnnotation.store())
                        .docValues(fieldAnnotation.docValues());
                propertyBuilder.binary(binaryPropertyBuilder.build());
                break;
            case Integer_Range:
                break;
            case Float_Range:
                break;
            case Long_Range:
                break;
            case Double_Range:
                break;
            case Date_Range:
                break;
            case Ip_Range:
                break;
            case Object:
                break;
            case Nested:
                CollectionType annotation = field.getAnnotation(CollectionType.class);
                Class<?> clas = annotation.clas();
                Map<String, Property> property = property(clas);
                NestedProperty.Builder builder = new NestedProperty.Builder();
                builder.properties(property);
                propertyBuilder.nested(builder.build());
                break;
            case Ip:
                break;
            case TokenCount:
                break;
            case Percolator:
                break;
            case Flattened:
                break;
            case Search_As_You_Type:
                break;
            case Rank_Feature:
                break;
            case Rank_Features:
                break;
            case Wildcard:
                break;
            case Dense_Vector:
                break;
        }
        return propertyBuilder.build();
    }

    /**
     * 构建字段属性
     *
     * @param field
     * @param fieldAnnotation
     * @return
     */
    public static Property property(Field field, org.springframework.data.elasticsearch.annotations.Field fieldAnnotation) {
        if (FieldType.Auto.equals(fieldAnnotation.type())) {
            //获取正确的类型,因为只有主键才会设置为Auto,主键为Integer、Long、string,故只需要判断两项即可
            Class<?> type = field.getType();
            if (Integer.class.equals(type)) {
                return property(field, fieldAnnotation, FieldType.Integer);
            } else if (Long.class.equals(type)) {
                return property(field, fieldAnnotation, FieldType.Long);
            } else if (String.class.equals(type)) {
                return property(field, fieldAnnotation, FieldType.Text);
            }
        }
        return property(field, fieldAnnotation, fieldAnnotation.type());
    }


    /**
     * 创建索引时的字段属性
     *
     * @param tClass
     * @param <T>
     * @return
     */
    public static <T> Map<String, Property> property(Class<T> tClass) {
        List<Field> fieldList = indexField(tClass);
        return property(fieldList);
    }

    /**
     * 创建索引时的字段属性
     *
     * @param fieldList
     * @param <T>
     * @return
     */
    public static <T> Map<String, Property> property(List<Field> fieldList) {
        Map<String, Property> propertyMap = new HashMap<>();
        fieldList.stream().forEach(field -> {
            org.springframework.data.elasticsearch.annotations.Field fieldAnnotation
                    = field.getAnnotation(org.springframework.data.elasticsearch.annotations.Field.class);
            String fieldName = field.getName();
            propertyMap.put(fieldName, property(field, fieldAnnotation));
        });
        return propertyMap;

    }

    /**
     * 获取文档内属性字段
     *
     * @param tClass
     * @param <T>
     * @return
     */
    public static <T> List<Field> indexField(Class<T> tClass) {
        List<Field> declaredFieldsAll = ReflectionUtil.getDeclaredFieldsAll(tClass);
        List<Field> fieldList = new ArrayList<>();
        declaredFieldsAll.stream().forEach(field -> {
            if (field.isAnnotationPresent(org.springframework.data.elasticsearch.annotations.Field.class)) {
                fieldList.add(field);
            }
        });
        return fieldList;

    }

}
